﻿using System;
using System.IO;
using System.Xml;
using Kent.Boogaart.Gauntlet;
using Kent.Boogaart.Gauntlet.InputTranslators;
using Kent.Boogaart.Gauntlet.OutputGenerators;
using NUnit.Framework;

namespace Kent.Boogaart.UnitTest.Gauntlet.InputTranslators
{
	[TestFixture]
	public sealed class XmlInputTranslatorTest
	{
		[Test]
		[ExpectedException(typeof(ArgumentNullException))]
		public void Translate_ShouldThrowIfContextIsNull()
		{
			XmlInputTranslator translator = new XmlInputTranslator();
			translator.Translate(null);
		}

		[Test]
		[ExpectedException(typeof(FileNotFoundException))]
		public void Translate_NonExistantInputFileShouldThrow()
		{
			XmlInputTranslator translator = new XmlInputTranslator();
			FileInfo inputFile = new FileInfo("doesnotexist");
			FileInfo templateFile = GetTempFile();
			FileInfo outputFile = GetTempFile();
			Context context = new Context() { InputFile = inputFile, OutputFile = outputFile, TemplateFile = templateFile, Timestamp = DateTime.Now };
			translator.Translate(context);
		}

		[Test]
		public void Translate_InvalidInputFileShouldThrow()
		{
			XmlInputTranslator translator = new XmlInputTranslator();
			FileInfo inputFile = GetTempFile("This isn't valid XML");
			FileInfo templateFile = GetTempFile();
			FileInfo outputFile = GetTempFile();
			Context context = new Context() { InputFile = inputFile, OutputFile = outputFile, TemplateFile = templateFile, Timestamp = DateTime.Now };

			try
			{
				translator.Translate(context);
				Assert.Fail("Was expecting exception.");
			}
			catch (XmlException ex)
			{
				Assert.AreEqual("Input file '" + inputFile.FullName + "' does not contain valid XML.", ex.Message);
			}
		}

		[Test]
		public void Translate_EmptyInputFileShouldThrow()
		{
			XmlInputTranslator translator = new XmlInputTranslator();
			FileInfo inputFile = GetTempFile("");
			FileInfo templateFile = GetTempFile();
			FileInfo outputFile = GetTempFile();
			Context context = new Context() { InputFile = inputFile, OutputFile = outputFile, TemplateFile = templateFile, Timestamp = DateTime.Now };

			try
			{
				translator.Translate(context);
				Assert.Fail("Was expecting exception.");
			}
			catch (XmlException ex)
			{
				Assert.AreEqual("Input file '" + inputFile.FullName + "' does not contain valid XML.", ex.Message);
			}
		}

		[Test]
		public void Translate_ShouldExposeRootElementViaState()
		{
			XmlInputTranslator translator = new XmlInputTranslator();
			FileInfo inputFile = GetTempFile("<TheRoot></TheRoot>");
			FileInfo templateFile = GetTempFile();
			FileInfo outputFile = GetTempFile();
			Context context = new Context() { InputFile = inputFile, OutputFile = outputFile, TemplateFile = templateFile, Timestamp = DateTime.Now };
			translator.Translate(context);
			Assert.IsTrue(context.State.ContainsKey("TheRoot"));
		}

		[Test]
		public void Translate_SingleChildrenShouldBeDirectlyNavigableViaPropertySyntax()
		{
			XmlInputTranslator translator = new XmlInputTranslator();
			NVelocityOutputGenerator generator = new NVelocityOutputGenerator();
			FileInfo inputFile = GetTempFile("<TheRoot><Child><GrandChild><GreatGrandChild>The value</GreatGrandChild></GrandChild></Child></TheRoot>");
			FileInfo templateFile = GetTempFile("$TheRoot");
			FileInfo outputFile = GetTempFile();
			Context context = new Context() { InputFile = inputFile, OutputFile = outputFile, TemplateFile = templateFile, Timestamp = DateTime.Now };
			translator.Translate(context);			
			generator.Generate(context);
			Assert.AreEqual("<Child><GrandChild><GreatGrandChild>The value</GreatGrandChild></GrandChild></Child>", File.ReadAllText(outputFile.FullName));

			templateFile = GetTempFile("$TheRoot.Child");
			context = new Context() { InputFile = inputFile, OutputFile = outputFile, TemplateFile = templateFile, Timestamp = DateTime.Now };
			translator.Translate(context);
			generator.Generate(context);
			Assert.AreEqual("<GrandChild><GreatGrandChild>The value</GreatGrandChild></GrandChild>", File.ReadAllText(outputFile.FullName));

			templateFile = GetTempFile("$TheRoot.Child.GrandChild");
			context = new Context() { InputFile = inputFile, OutputFile = outputFile, TemplateFile = templateFile, Timestamp = DateTime.Now };
			translator.Translate(context);
			generator.Generate(context);
			Assert.AreEqual("<GreatGrandChild>The value</GreatGrandChild>", File.ReadAllText(outputFile.FullName));

			templateFile = GetTempFile("$TheRoot.Child.GrandChild.GreatGrandChild");
			context = new Context() { InputFile = inputFile, OutputFile = outputFile, TemplateFile = templateFile, Timestamp = DateTime.Now };
			translator.Translate(context);
			generator.Generate(context);
			Assert.AreEqual("The value", File.ReadAllText(outputFile.FullName));
		}

		[Test]
		public void Translate_MultipleChildrenShouldBeIterableViaPropertySyntax()
		{
			XmlInputTranslator translator = new XmlInputTranslator();
			NVelocityOutputGenerator generator = new NVelocityOutputGenerator();
			FileInfo inputFile = GetTempFile("<TheRoot><Child><GrandChild>1</GrandChild><NotAGrandChild>Hello</NotAGrandChild><GrandChild>2</GrandChild><GrandChild>3</GrandChild><NotAGrandChild>There</NotAGrandChild></Child></TheRoot>");
			FileInfo templateFile = GetTempFile("#foreach ($grandChild in $TheRoot.Child.GrandChild)$grandChild#end");
			FileInfo outputFile = GetTempFile();
			Context context = new Context() { InputFile = inputFile, OutputFile = outputFile, TemplateFile = templateFile, Timestamp = DateTime.Now };
			translator.Translate(context);
			generator.Generate(context);
			Assert.AreEqual("123", File.ReadAllText(outputFile.FullName));

			templateFile = GetTempFile("#foreach ($notAGrandChild in $TheRoot.Child.NotAGrandChild)$notAGrandChild#end");
			context = new Context() { InputFile = inputFile, OutputFile = outputFile, TemplateFile = templateFile, Timestamp = DateTime.Now };
			translator.Translate(context);
			generator.Generate(context);
			Assert.AreEqual("HelloThere", File.ReadAllText(outputFile.FullName));
		}

		[Test]
		public void Translate_AttributesShouldBeAccessibleViaMethodSyntax()
		{
			XmlInputTranslator translator = new XmlInputTranslator();
			NVelocityOutputGenerator generator = new NVelocityOutputGenerator();
			FileInfo inputFile = GetTempFile("<TheRoot Att='Some Value'><Child><GrandChild Name='Tempany' Age='3'/></Child></TheRoot>");
			FileInfo templateFile = GetTempFile("$TheRoot.Child.GrandChild.Name is $TheRoot.Child.GrandChild.Age");
			FileInfo outputFile = GetTempFile();
			Context context = new Context() { InputFile = inputFile, OutputFile = outputFile, TemplateFile = templateFile, Timestamp = DateTime.Now };
			translator.Translate(context);
			generator.Generate(context);
			Assert.AreEqual("Tempany is 3", File.ReadAllText(outputFile.FullName));

			templateFile = GetTempFile("$TheRoot.Att");
			context = new Context() { InputFile = inputFile, OutputFile = outputFile, TemplateFile = templateFile, Timestamp = DateTime.Now };
			translator.Translate(context);
			generator.Generate(context);
			Assert.AreEqual("Some Value", File.ReadAllText(outputFile.FullName));
		}

		[Test]
		public void Translate_InvalidPropertyShouldYieldExpressionIntact()
		{
			XmlInputTranslator translator = new XmlInputTranslator();
			NVelocityOutputGenerator generator = new NVelocityOutputGenerator();
			FileInfo inputFile = GetTempFile("<TheRoot><Child><GrandChild/></Child></TheRoot>");
			FileInfo templateFile = GetTempFile("$Root $TheRoot.GrandChild $TheRoot.Child.GrandChild.Something ${TheRoot.Child.GrandChild.Something}");
			FileInfo outputFile = GetTempFile();
			Context context = new Context() { InputFile = inputFile, OutputFile = outputFile, TemplateFile = templateFile, Timestamp = DateTime.Now };
			translator.Translate(context);
			generator.Generate(context);
			Assert.AreEqual("$Root $TheRoot.GrandChild $TheRoot.Child.GrandChild.Something ${TheRoot.Child.GrandChild.Something}", File.ReadAllText(outputFile.FullName));
		}

		[Test]
		public void Translate_InvalidAttributeShouldYieldExpressionIntact()
		{
			XmlInputTranslator translator = new XmlInputTranslator();
			NVelocityOutputGenerator generator = new NVelocityOutputGenerator();
			FileInfo inputFile = GetTempFile("<TheRoot><Child/></TheRoot>");
			FileInfo templateFile = GetTempFile("$TheRoot.Att $TheRoot.Child.Att");
			FileInfo outputFile = GetTempFile();
			Context context = new Context() { InputFile = inputFile, OutputFile = outputFile, TemplateFile = templateFile, Timestamp = DateTime.Now };
			translator.Translate(context);
			generator.Generate(context);
			Assert.AreEqual("$TheRoot.Att $TheRoot.Child.Att", File.ReadAllText(outputFile.FullName));
		}

		[Test]
		public void Translate_NameMethodShouldGetElementName()
		{
			XmlInputTranslator translator = new XmlInputTranslator();
			NVelocityOutputGenerator generator = new NVelocityOutputGenerator();
			FileInfo inputFile = GetTempFile("<TheRoot><Child/></TheRoot>");
			FileInfo templateFile = GetTempFile("$TheRoot.Name() $TheRoot.Child.Name()");
			FileInfo outputFile = GetTempFile();
			Context context = new Context() { InputFile = inputFile, OutputFile = outputFile, TemplateFile = templateFile, Timestamp = DateTime.Now };
			translator.Translate(context);
			generator.Generate(context);
			Assert.AreEqual("TheRoot Child", File.ReadAllText(outputFile.FullName));
		}

		[Test]
		public void Translate_CountMethodShouldGetChildCount()
		{
			XmlInputTranslator translator = new XmlInputTranslator();
			NVelocityOutputGenerator generator = new NVelocityOutputGenerator();
			FileInfo inputFile = GetTempFile("<TheRoot><Child/><Something><SomethingElse/></Something></TheRoot>");
			FileInfo templateFile = GetTempFile("$TheRoot.Count() $TheRoot.Child.Count() $TheRoot.Something.Count()");
			FileInfo outputFile = GetTempFile();
			Context context = new Context() { InputFile = inputFile, OutputFile = outputFile, TemplateFile = templateFile, Timestamp = DateTime.Now };
			translator.Translate(context);
			generator.Generate(context);
			Assert.AreEqual("2 0 1", File.ReadAllText(outputFile.FullName));
		}

		[Test]
		public void Translate_CountMethodShouldGetChildCountForSpecifiedXPathExpression()
		{
			XmlInputTranslator translator = new XmlInputTranslator();
			NVelocityOutputGenerator generator = new NVelocityOutputGenerator();
			FileInfo inputFile = GetTempFile("<TheRoot><Child/><Child/><Something><SomethingElse/></Something></TheRoot>");
			FileInfo templateFile = GetTempFile("$TheRoot.Count('Child') $TheRoot.Count('Something') $TheRoot.Count('Something/SomethingElse')");
			FileInfo outputFile = GetTempFile();
			Context context = new Context() { InputFile = inputFile, OutputFile = outputFile, TemplateFile = templateFile, Timestamp = DateTime.Now };
			translator.Translate(context);
			generator.Generate(context);
			Assert.AreEqual("2 1 1", File.ReadAllText(outputFile.FullName));
		}

		[Test]
		public void Translate_ChildrenMethodShouldGetAllChildren()
		{
			XmlInputTranslator translator = new XmlInputTranslator();
			NVelocityOutputGenerator generator = new NVelocityOutputGenerator();
			FileInfo inputFile = GetTempFile("<TheRoot><Child/><ADifferentChild/><Child><Grandchild/></Child></TheRoot>");
			FileInfo templateFile = GetTempFile("#foreach ($child in $TheRoot.Children())$child.Name() #end");
			FileInfo outputFile = GetTempFile();
			Context context = new Context() { InputFile = inputFile, OutputFile = outputFile, TemplateFile = templateFile, Timestamp = DateTime.Now };
			translator.Translate(context);
			generator.Generate(context);
			Assert.AreEqual("Child ADifferentChild Child ", File.ReadAllText(outputFile.FullName));
		}

		[Test]
		public void Translate_ChildrenMethodShouldGetAllChildrenForSpecifiedXPathExpression()
		{
			XmlInputTranslator translator = new XmlInputTranslator();
			NVelocityOutputGenerator generator = new NVelocityOutputGenerator();
			FileInfo inputFile = GetTempFile("<TheRoot><Child/><ADifferentChild/><Child><Grandchild/></Child></TheRoot>");
			FileInfo templateFile = GetTempFile("#foreach ($child in $TheRoot.Children('Child'))$child.Name() #end");
			FileInfo outputFile = GetTempFile();
			Context context = new Context() { InputFile = inputFile, OutputFile = outputFile, TemplateFile = templateFile, Timestamp = DateTime.Now };
			translator.Translate(context);
			generator.Generate(context);
			Assert.AreEqual("Child Child ", File.ReadAllText(outputFile.FullName));

			templateFile = GetTempFile("#foreach ($child in $TheRoot.Children('Child/Grandchild'))$child.Name() #end");
			outputFile = GetTempFile();
			context = new Context() { InputFile = inputFile, OutputFile = outputFile, TemplateFile = templateFile, Timestamp = DateTime.Now };
			translator.Translate(context);
			generator.Generate(context);
			Assert.AreEqual("Grandchild ", File.ReadAllText(outputFile.FullName));
		}

		private static FileInfo GetTempFile()
		{
			return GetTempFile(null);
		}

		private static FileInfo GetTempFile(string contents)
		{
			string file = Path.GetTempFileName();

			if (contents != null)
			{
				using (StreamWriter streamWriter = File.CreateText(file))
				{
					streamWriter.Write(contents);
				}
			}

			return new FileInfo(file);
		}
	}
}